package org.stragma.apply.modules

import org.kiama.rewriting.Rewriter.{attempt,bottomup,fail,id,oncetd,outermost,rulefs,Term}

import org.stragma.apply.signatures.Apply._
import org.stragma.apply.signatures.Conversions._
import org.stragma.apply.signatures.Options._
import org.stragma.apply.modules._
import org.stragma.apply.modules.ApplyStrategies._
import org.stragma.apply.modules.Options._
import org.stragma.apply.util._
import org.stragma.apply.util.LogicalOperators._

import org.stragma.util.ListManager._

class Doapply {

	// Declaration of global rules (contextual)
	var WindowScalar = fail
	var WindowElement = fail

	/**
	 *	Primary doApply strategy
	 */
	def Apply_Doapply = topdownstopping(add_loop) <* 
						bottomup(attempt(window_procedure)) <* 
						topdownstopping(propagate_looptype)

	/*
	 * Collects the top most occurrences of the WindowArray term
	 * and returns it in a list
	 */
	def collect_windows = 
		rulefs {
			case t:Term =>
				var windows = List[Term]()
		
				// This inner rule matches the term at the top most level
				// and adds it to a local list
				outermost(rulefs {
					case WindowArray(rowrange,colrange,typ) =>
						windows ::= WindowArray(rowrange,colrange,typ)
						TTypeToTArgType(typ) // This is weird?
					})(t)
		
					windows
		}

	/*
	 * Selection of the required dimension: rs, re, cs or ce.
	 */
	def select_rs = 
		rulefs { 
			case WindowArray(Range2(Int(rs),Int(re)),Range2(Int(cs),Int(ce)),t) => rs 
		}
	
	def select_re = 
		rulefs { 
			case WindowArray(Range2(Int(rs),Int(re)),Range2(Int(cs),Int(ce)),t) => re 
		}
	
	def select_cs = 
		rulefs { 
			case WindowArray(Range2(Int(rs),Int(re)),Range2(Int(cs),Int(ce)),t) => cs 
		}
	
	def select_ce = 
		rulefs { 
			case WindowArray(Range2(Int(rs),Int(re)),Range2(Int(cs),Int(ce)),t) => ce 
		}

	/*
	 * Calculation of the most extreme value in each of the required dimensions, to be applied to collected windows
	 */
	def min_rs = foldr("0",minstring,select_rs)
	def max_re = foldr("0",maxstring,select_re)
	def min_cs = foldr("0",minstring,select_cs)
	def max_ce = foldr("0",maxstring,select_ce)
	
	/*
	 * Calculation of the set of extreme dimensions from the collected windows
  	 * collection-extreme-dimensions = ?collection; !(Int(<min-rs>collection), Int(<max-re>collection), Int(<min-cs>collection), Int(<max-ce>collection))
	 */
	def collection_extreme_dimensions = 
		rulefs {
			case winArr : List[WindowArray] => // Corollary to ?collection
				(Int((min_rs(winArr).get).toString),
				Int((max_re(winArr).get).toString),
				Int((min_cs(winArr).get).toString),
				Int((max_ce(winArr).get).toString))
		}

	/*
	 * Calculation of the set of extreme dimensions for argument list
  	 * argument-extreme-dimensions = collect-windows ; collection-extreme-dimensions
	 */
	def argument_extreme_dimensions = collect_windows <* collection_extreme_dimensions

	/*
	 * Strategy to code references to window arguments as WindowAccess
  	 * Prerequisite: there must be one variable for each type in the arglist.
  	 * The following strategy generates dyanmic rules to recode variables that are window accesses
  	 * into WindowAcessScalar or WindowAccessElement.
  	 * WindowScalar : Var("img") -> WindowAccessScalar(Var("img"))
  	 * WindowElement : Func(Var("img"),Comma(i,j)) -> WindowAccessElement(Var("img"),i,j,Range(rs,re),Range(cs,ce))
  	 * It is an error to access a scalar window with subscript or a non-scalar window without subscripts
	 */
	def window_declaration = 
		rulefs {
			case Argument(x : Var, Access(a,Window(typ))) =>
				
				WindowScalar = WindowScalar <+ 
					rulefs {
						case Func ( y : Var, x : Var ) => Func(y,WindowAccessScalar(x,typ))
						case Geq ( x : Var, y : Var ) => Geq(WindowAccessScalar(x,typ),y)
						case Lt ( x : Var, y : Var ) => Lt(WindowAccessScalar(x,typ),y)
						case y : Var if (x == y) => WindowAccessScalar(y, typ)
					}
					
				id
				
			case Argument(x,Access(a,WindowArray(rowrange,colrange,typ))) =>

				WindowElement = WindowElement <+ 
					rulefs {
						case Func(x:Var,Comma(row,col)) =>
							TVarToTScalarVar(WindowAccessElement(x,row,col,rowrange,colrange,typ))
					}
				
				id
	}

	/*
	 * Port of strategy
	 * window-use = topdownstopping(WindowScalar <+ WindowElement)
	 */
	def window_use = topdownstopping(WindowScalar <+ WindowElement)

	/*
	 * Process all window declarations to generate the dyanmic rules
  	 * window-declarations = bottomup(try(window-declaration))
	 */
	def window_declarations = bottomup(attempt(window_declaration))

	/*
	 * Process window declarations and references within a procedure:
  	 * First, we apply window-declarations to the procedure argument list,
  	 * then we apply window-use to the body of the procedure.
  	 * window-procedure = ?Procedure(name, args, decs, stmts, ename)
     *; {| WindowElement, WindowScalar :   Procedure(id, window-declarations, id, id, id)
     *                                  ; Procedure(id, id, id, window-use, id) |}
	 */
	def window_procedure = rulefs {
		case Procedure(name, args, decs, stmts, ename) =>
			// Take a snapshot of the global rule states in order to restore after the rule completes executing
			val orgWindowScalar = WindowScalar
			val orgWindowElement = WindowElement
			
			// Need to casts because the returned values are raw terms
			val modArgs = (window_declarations(args).get).asInstanceOf[TFunctionArgs]
			val modStmts = (window_use(stmts).get).asInstanceOf[TStatements]
			
			// Return the strategies back to their original state
			WindowScalar = orgWindowScalar
			WindowElement = orgWindowElement
			Procedure(name,modArgs,decs,modStmts,ename)
	}

	/*
	 * Port of strategy
	 * Add the apply loop (extended syntax) to the procedure body.
  	 * add-loop = ?Procedure(name, args, decs, stmts, ename)
     * 		; where(<argument-extreme-dimensions>args => (rs,re,cs,ce))
     *	   ; if ( oncetd(?Var("row")) <+ oncetd(?Var("column"))) then 
	 *				!Procedure(name, args, decs,Statements( [ ApplyLoop(RowColumn, rs, re, cs, ce, stmts) ] ), ename)
     * 		else 
	 *				!Procedure(name, args, decs,Statements( [ ApplyLoop(Index, rs, re, cs, ce, stmts) ] ), ename )
     * 		end
	 */
	def add_loop = rulefs {
		case Procedure(name,args,decs,stmts,ename) =>
			val res : (Int,Int,Int,Int) = (argument_extreme_dimensions(args).get).asInstanceOf[(Int,Int,Int,Int)]
			
			if (((oncetd(rulefs { case Var("row") => id }) <+ 
				oncetd(rulefs { case Var("column") => id }))(Procedure(name,args,decs,stmts,ename))).isDefined)
				Procedure(name,args,decs,Statements(List(ApplyLoop(RowColumn(),res._1,res._2,res._3,res._4,stmts))),ename)
			else
				Procedure(name,args,decs,Statements(List(ApplyLoop(Index(),res._1,res._2,res._3,res._4,stmts))),ename)
	}

	/*
	 * Port of strategy
	 * Propagate loop type down to access operations
  	 * propagate-looptype = ?ApplyLoop(looptype, rs, re, cs, ce, stmts); topdownstopping(add-type(|looptype))
	 */
	def propagate_looptype = 
		rulefs {
			case ApplyLoop(looptype,rs,re,cs,ce,stmts) => topdownstopping(add_type(looptype))
		}
	
	/*
	 * Port of rules
	 * add-type(|looptype) : WindowAccessScalar(x,t) -> WindowAccessScalar(x,t,looptype)
	 * add-type(|looptype) : WindowAccessElement(x,row,col,rowrange,colrange,t) ->                     WindowAccessElement(x,row,col,rowrange,colrange,t,looptype)
	 */
	def add_type ( loopType : TLoopType ) = 
		rulefs{
			case WindowAccessScalar(x,t) => WindowAccessScalar2(x,t,loopType)
			case WindowAccessElement(x,row,col,rowrange,colrange,t) => WindowAccessElement1(x,row,col,rowrange,colrange,t,loopType)
		}
	
	/*
	 * Helper method to invoke function from Java
	 */
	def doApplyHelper(term : Term) : Term = (Apply_Doapply(term)).get
	
	/*
	 * This helper method executes doApply n times and returns
	 * the very last result the caller
	 */
	def doApplyHelper(term : Term, n : scala.Int) : Term = {
		var result : Term = scala.None
		for(i <- 0 until n) {
			result = (Apply_Doapply(term)).get
			
			// Added the println to mimic the way strategoxt.jar works
			println(result)
		}
		
		result
	}
	
	/*
	 * This helper method executes doApply n times and returns
	 * the very last result to the caller
	 */
	def doApplyHelper(file : String, n : scala.Int) : Term = {
		val iTerm = loadTermFromFile(file)
		doApplyHelper(iTerm, n)
	}
}